// ==UserScript==
// @name         xiaohongshuTest01
// @namespace    http://tampermonkey.net/
// @version      2025-01-01
// @description  try to take over the world!
// @author       You
// @match        https://www.xiaohongshu.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=xiaohongshu.com
// @require https://update.greasyfork.org/scripts/524640/1525620/exceljs%20440.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        unsafeWindow
// ==/UserScript==

(function () {
    /* 大坑，其他插件扩展影响到了油猴插件，调试前先关闭其他扩展 */
    'use strict';
    let postCommentsObject;
    var postDataObject;
    var lastArrayLen;
    var alreadyClickedIndex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '26', '27'];
    const interceptStrings = ['sns/web/v1/feed', 'web/v2/comment'];

    class PostManager {
        constructor(expectedOpenCount, openedCount = 0, storedCount = 0, createXhsExcelObject, createWorkbook) {
            this.expectedOpenCount = expectedOpenCount; // 预计打开数量
            this.openedCount = openedCount; // 已打开数量，默认为0
            this.storedCount = storedCount; // 已存储数量，默认为0
            this.createXhsExcelObject = createXhsExcelObject || (() => new XhsExcelObject());
            this.createWorkbook = createWorkbook || (() => new ExcelJS.Workbook());
        }

        // 打开帖子的方法
        async openPost() {
            var xhsExcelObjects = [];
            if (this.openedCount < this.expectedOpenCount) {
                let postElementArray = [...document.querySelectorAll('#exploreFeeds > section')];
                for (const postElement of postElementArray) {
                    // 重试函数不要用try catch 包含，有问题
                    // 尝试点击打开帖子
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    await this.retryWithDelay(() => {
                        const targetElement = postElement.querySelector('div>a:nth-child(2)');
                        if (!targetElement) throw new Error('目标元素不存在');
                        postDataObject = null;
                        targetElement.click();
                    }, 500, 3);
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    await this.retryWithDelay(async () => {
                        if (!postDataObject) throw new Error('未能获取帖子数据');
                        if (!postCommentsObject) throw new Error('未能获取帖子数据');
                        return postDataObject;
                    }, 400, 8); // 每次重试等待200ms，最多重试5次
                    let postDataObjectItem = postDataObject.data.items[0];
                    let note_id = postDataObjectItem.id;
                    let desc = postDataObjectItem.note_card.desc;
                    let media_image_list = postDataObjectItem.note_card.image_list.map(item => item.url_default);
                    let user_id = postDataObjectItem.note_card.user.user_id;
                    let title = postDataObjectItem.note_card.title;
                    let note_type = postDataObjectItem.note_card.type;
                    let tag_list = postDataObjectItem.note_card.tag_list.map(item => item.name);
                    let collected_count = postDataObjectItem.note_card.interact_info.collected_count;
                    let comment_count = postDataObjectItem.note_card.interact_info.comment_count;
                    let liked_count = postDataObjectItem.note_card.interact_info.liked_count;
                    let share_count = postDataObjectItem.note_card.interact_info.share_count;
                    let media_video = postDataObjectItem.note_card.video?.media.stream.h264[0]?.master_url;
                    let ip_location = postDataObjectItem.note_card.ip_location;
                    // 使用传入的工厂函数创建 XhsExcelObject
                    const xhsExcelObject = this.createXhsExcelObject(user_id, desc, media_image_list, ip_location,
                        collected_count, comment_count, liked_count, share_count, note_id, tag_list,
                        title, note_type, media_video
                    );
                    //评论内容
                    /*                     postCommentsObject.data.comment.for
                                            xhsExcelObject.addComment(); */

                    xhsExcelObjects.push(xhsExcelObject);

                    await this.retryWithDelay(() => {
                        const closeButton = document.querySelector('body > div.note-detail-mask > div.close-circle > div');
                        if (!closeButton) throw new Error('关闭按钮不存在');
                        closeButton.click();
                    }, 200, 6);
                    this.openedCount++;
                    console.log(`已打开帖子，当前已打开数量: ${this.openedCount}`);
                }
                return xhsExcelObjects;
            } else {
                console.log('所有预计的帖子都已打开');
            }
        }


        // 打开帖子的方法
        async openLikePost() {
            var xhsExcelObjects = [];
            if (this.openedCount < this.expectedOpenCount) {
                let postElementArray = [...document.querySelectorAll('div.feeds-container > section')];
                const postSnapshot = Array.from(document.querySelectorAll('div.feeds-container > section'))
                    .map(el => ({
                        element: el,
                        rect: el.getBoundingClientRect(),
                        index: el.getAttribute('data-index')
                    }));
                // if(lastArrayLen){
                //     const lengthNew = Math.max(0, postElementArray.length - lastArrayLen);
                //     postElementArray = [...postElementArray.slice(lengthNew)]
                // }
                for (const { element: postElement, index: postIndex } of postSnapshot) {
                    // 重试函数不要用try catch 包含，有问题
                    // 尝试点击打开帖子
                    // var postIndex = postElement.getAttribute('data-index');
                    if (postIndex == '28') {
                        debugger;
                    };
                    await this.scrollToElement(postElement);
                    if (alreadyClickedIndex.includes(postIndex)) {
                        continue;
                    }
                    alreadyClickedIndex.push(postIndex);
                    await new Promise(resolve => setTimeout(resolve, 500));
                    await this.retryWithDelay(() => {
                        const targetElement = postElement.querySelector('div>a:nth-child(2)');
                        if (!targetElement) throw new Error('目标元素不存在');
                        postDataObject = null;
                        targetElement.click();
                    }, 500, 3);
                    await new Promise(resolve => setTimeout(resolve, 1500));
                    // 修改数据等待部分
                    await this.retryWithDelay(async () => {
                        if (!postDataObject || !postCommentsObject) {
                            throw new Error('数据未就绪');
                        }
                        // 验证数据对应性
                        if (postDataObject.data.items[0].id !==
                            postElement.querySelector('div>a:nth-child(1)').getAttribute('href').split('/')[2]) {
                            postDataObject = null; // 重置错误数据
                            throw new Error('数据不匹配当前帖子');
                        }
                        return true;
                    }, 1000, 15); // 延长等待时间
                    let postDataObjectItem = postDataObject.data.items[0];
                    let note_id = postDataObjectItem.id;
                    let desc = postDataObjectItem.note_card.desc;
                    let media_image_list = postDataObjectItem.note_card.image_list.map(item => item.url_default);
                    let user_id = postDataObjectItem.note_card.user.user_id;
                    let title = postDataObjectItem.note_card.title;
                    let note_type = postDataObjectItem.note_card.type;
                    let tag_list = postDataObjectItem.note_card.tag_list.map(item => item.name);
                    let collected_count = postDataObjectItem.note_card.interact_info.collected_count;
                    let comment_count = postDataObjectItem.note_card.interact_info.comment_count;
                    let liked_count = postDataObjectItem.note_card.interact_info.liked_count;
                    let share_count = postDataObjectItem.note_card.interact_info.share_count;
                    let media_video = postDataObjectItem.note_card.video?.media.stream.h264[0]?.master_url;
                    let ip_location = postDataObjectItem.note_card.ip_location;
                    // 使用传入的工厂函数创建 XhsExcelObject
                    const xhsExcelObject = this.createXhsExcelObject(user_id, desc, media_image_list, ip_location,
                        collected_count, comment_count, liked_count, share_count, note_id, tag_list,
                        title, note_type, media_video
                    );
                    //评论内容
                    /*                     postCommentsObject.data.comment.for
                                            xhsExcelObject.addComment(); */

                    xhsExcelObjects.push(xhsExcelObject);

                    // 修改关闭操作部分
                    await this.retryWithDelay(async () => {
                        const closeButton = await this.waitForCloseButton();
                        await closeButton.click();
                        return this.confirmCloseSuccess(); // 新增关闭验证

                        // 新增重置操作
                        postDataObject = null;
                        postCommentsObject = null;
                        return true;
                    }, 500, 8);
                    this.openedCount++;
                    console.log(`已打开帖子，当前已打开数量: ${this.openedCount}`);
                }
                lastArrayLen = postElementArray.length;
                return xhsExcelObjects;
            } else {
                console.log('所有预计的帖子都已打开');
            }
        }

        // 新增辅助方法
        async waitForCloseButton() {
            const maxWait = 5000; // 5秒超时
            const start = Date.now();
            while (Date.now() - start < maxWait) {
                const btn = document.querySelector('body > div.note-detail-mask > div.close-circle > div');
                if (btn) return btn;
                await this.delay(200);
            }
            throw new Error('关闭按钮未找到');
        }

        confirmCloseSuccess() {
            return this.retryWithDelay(() => {
                const mask = document.querySelector('div.note-detail-mask');
                if (mask && getComputedStyle(mask).display !== 'none') {
                    throw new Error('弹窗未正确关闭');
                }
            }, 300, 5);
        }

        async convertToExcel(xhsExcelObjects) {
            const workbook = this.createWorkbook(); // 使用传入的工厂函数创建 Workbook

            const worksheet = workbook.addWorksheet('Sheet 1');

            // 添加表头
            worksheet.addRow(['user_id', 'desc', 'media_image_list', 'ip_location', 'collected_count', 'comment_count',
                'liked_count', 'share_count', 'note_id', 'tag_list', 'title', 'note_type', 'media_video']);

            // 遍历 XhsExcelObject 数组并添加行
            for (const obj of xhsExcelObjects) {
                worksheet.addRow([obj.user_id, obj.desc, obj.media_image_list, obj.ip_location, obj.collected_count,
                obj.comment_count, obj.liked_count, obj.share_count, obj.note_id, obj.tag_list, obj.title,
                obj.note_type, obj.media_video
                ]);
            }

            // 返回工作簿对象，或者你可以保存为文件等操作
            return workbook;
        }

        // 重试机制辅助函数
        async retryWithDelay(fn, delay, maxRetries) {
            let attempt = 0;
            while (attempt < maxRetries) {
                try {
                    return await fn();
                } catch (error) {
                    if (attempt === maxRetries - 1) throw error; // 如果是最后一次尝试，则抛出错误
                    await this.delay(delay);
                    attempt++;
                }
            }
        }

        // 模拟延迟的辅助函数
        delay(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        async ifLoaded() {
            let loadSvgDiv = document.querySelector('#feeds-replace-loading');
            this.retryWithDelay(async () => {
                if (!(loadSvgDiv.style.height === '0px')) throw new Error('加载中');
                return loadSvgDiv;
            }, 1000, 5);
        }

        // 帖子初步筛选的方法
        preliminaryFilter() {
            // 1. 已存储的跳过
            console.log('正在进行帖子初步筛选...');
        }

        // 存储帖子数据
        storePostData() {

        }
        // 返回自身全部属性的数组的方法
        getAttributesArray() {
            return ['expectedOpenCount', 'openedCount', 'storedCount'].map(attr => this[attr]);
        }

        // // 获取帖子及评论数据 这种方式打开的抓不到帖子内容
        // async getPostAndCommentData(url, title = '_blank', w = 800, h = 600) {
        //     // 检查参数有效性
        //     if (!url || typeof url !== 'string') {
        //         console.error('Invalid URL provided');
        //         return null;
        //     }

        //     // 设置默认标题和窗口尺寸
        //     title = title || '_blank';
        //     w = Math.min(w, screen.availWidth);
        //     h = Math.min(h, screen.availHeight);

        //     // 计算居中位置
        //     let x = (screen.availWidth - w) / 2;
        //     let y = (screen.availHeight - h) / 2;

        //     // 确保窗口不会超出屏幕边界
        //     x = Math.max(x, 0);
        //     y = Math.max(y, 0);

        //     // 打开新窗口并等待其加载完成
        //     let win;
        //     try {
        //         win = window.open(url, title, `width=${w},height=${h},left=${x},top=${y}`);
        //         if (!win) {
        //             throw new Error('Failed to open the window');
        //         }
        //         win.focus();

        //         // 清除旧的数据
        //         await GM.setValue('postDataObject', '');
        //         await GM.setValue('postCommentDataObject', '');

        //         // 监听新窗口的加载完成事件
        //         await new Promise((resolve, reject) => {
        //             win.addEventListener('load', () => {
        //                 resolve();
        //             });

        //             win.addEventListener('error', (event) => {
        //                 reject(new Error(`Failed to load page: ${event.message}`));
        //             });

        //             // 如果窗口已经加载完成，则立即resolve
        //             if (win.document.readyState === 'complete') {
        //                 resolve();
        //             }
        //         });
        //     } catch (e) {
        //         console.error('Error opening or loading the window:', e);
        //         return null;
        //     }

        //     return win;
        // }
        // 清除所有属性的方法
        reset() {
            // 使用构造函数参数的默认值来重置属性
            Object.assign(this, new PostManager());
        }

        // 辅助函数：平滑滚动到元素中心点
        scrollToElement(element) {
            return new Promise((resolve) => {
                element.scrollIntoView({ behavior: 'smooth', block: 'center' });
                // 等待滚动动画完成
                setTimeout(resolve, 500); // 假设滚动动画持续时间不超过500ms
            });
        }
    }

    class XhsExcelObject {
        /* 
            user_id: 帖子发布者id; desc:帖子内容+tag(不包括标题); media_image_list:图片资源列表;
            ip_location: 发布者ip地址; collected_count: 收藏数; comment_count: 评论数; liked_count: 点赞数;
            share_count: 转发数; note_id: 帖子id; tag_list: 标签列表; title: 帖子标题; note_type: 帖子类型(normal/video);
            media_video: 视频资源地址
        */
        constructor(user_id, desc, media_image_list, ip_location, collected_count, comment_count,
            liked_count, share_count, note_id, tag_list, title, note_type, media_video
        ) {
            this.user_id = user_id;
            this.desc = desc;
            this.media_image_list = media_image_list;
            this.ip_location = ip_location;
            this.collected_count = collected_count;
            this.comment_count = comment_count;
            this.liked_count = liked_count;
            this.share_count = share_count;
            this.note_id = note_id;
            this.tag_list = tag_list;
            this.title = title;
            this.note_type = note_type;
            this.media_video = media_video;
            this.comments = [];
        }

        /* 
            content: 评论内容;like_count: 点赞数;ip_location: 评论ip地址;
            note_id: 帖子id;show_tags: 是否显示标签;pictures: 图片资源列表;
            sub_comment_count: 子评论数;
        */
        // 添加评论的方法
        addComment(content, like_count = 0, ip_location, note_id, show_tags, pictures, sub_comment_count,
            user_id, create_time) {
            const comment = {
                content,
                like_count,
                ip_location,
                note_id,
                show_tags,
                pictures,
                sub_comment_count,
                user_id,
                create_time
            };
            this.comments.push(comment);
        }

        // 清除所有属性的方法
        reset() {
            // 使用构造函数参数的默认值来重置属性
            Object.assign(this, new XhsExcelObject());
        }
    }

    function createXhsExcelObjectFactory(user_id, desc, media_image_list, ip_location, collected_count, comment_count,
        liked_count, share_count, note_id, tag_list, title, note_type, media_video) {
        return new XhsExcelObject(user_id, desc, media_image_list, ip_location, collected_count, comment_count,
            liked_count, share_count, note_id, tag_list, title, note_type, media_video
        );
    }

    function createExcelJsWorkBookFactory() {
        return new ExcelJS.Workbook();
    }

    const originalOpen = XMLHttpRequest.prototype.open;


    // 重写 XMLHttpRequest.prototype.open 函数
    XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
        // 检查是否为需要监听的特定URL
        const targetSubstrings = interceptStrings;
        if (targetSubstrings.some(substring => url.includes(substring))) {
            // 继续使用原始的 open 方法
            originalOpen.apply(this, arguments);

            // 添加 readystatechange 事件监听器
            this.addEventListener('readystatechange', function () {
                // 当请求完成时
                if (this.readyState === 4 && this.status >= 200 && this.status < 300) { // 确保响应成功
                    // console.log('Response received:', this.responseText);
                    if (this.responseURL.includes('sns/web/v1/feed')) {
                        postDataObject = JSON.parse(this.responseText);
                        if (postDataObject) {
                            let postDataObjectItem = postDataObject.data.items[0];
                            let title = postDataObjectItem.note_card.title;
                            console.log(title)
                        }

                    }
                    if (this.responseURL.includes('web/v2/comment')) {
                        postCommentsObject = JSON.parse(this.responseText);
                    }
                }
            }, false);
        } else {
            // 对于其他URL，直接调用原始的 open 方法，不添加额外逻辑
            originalOpen.apply(this, arguments);
        }
    };


    // 使用原始 fetch 函数
    const originalFetch = unsafeWindow.fetch;

    // 覆盖 fetch 函数
    unsafeWindow.fetch = async function (url, options) {
        // 只拦截包含特定子串的 URL
        const targetSubstrings = interceptStrings;
        if (targetSubstrings.some(substring => url.includes(substring))) {
            try {
                // 使用原始 fetch 发起请求并等待响应
                const response = await originalFetch(url, options);

                // 克隆响应以确保可以安全地读取body而不影响后续使用
                const clonedResponse = response.clone();

                // 如果需要，可以在这里修改 response
                // 确保响应成功（状态码200-299）
                if (response.ok) {
                    // 将响应内容转换为文本并打印出来
                    const text = await clonedResponse.text();
                    console.log('Intercepted URL:', url);
                    console.log('Response received:', text);

                    // 返回原始响应对象，不影响正常流程
                    return response;
                } else {
                    // 处理非成功的HTTP响应代码
                    console.error('Failed response from:', url);
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
            } catch (error) {
                // 捕获和处理错误
                console.error('Error during fetch:', error);
                throw error;
            }
        } else {
            // 对于其他URL，直接调用原始的 fetch 函数，不添加额外逻辑
            return originalFetch(url, options);
        }
    };

    var currentUrl = window.location.href;
    if (!(currentUrl.includes('xsec_token'))) {
        // window.addEventListener('load', async function () {
        //     const postManager = new PostManager(18 * 8, 0, 0, createXhsExcelObjectFactory, createExcelJsWorkBookFactory);
        //     var xhsExcelObjects = await postManager.openPost();
        //     // const xhsExcelObjects = [];
        //     const restoreNum = 18 * 2;
        //     while (postManager.openedCount < postManager.expectedOpenCount) {

        //         if (xhsExcelObjects.length >= restoreNum) {
        //             const excelWorkBook = await postManager.convertToExcel(xhsExcelObjects);
        //             // 导出 Excel 文件
        //             excelWorkBook.xlsx.writeBuffer().then(buffer => {
        //                 const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
        //                 const url = URL.createObjectURL(blob);
        //                 const a = document.createElement('a');
        //                 a.href = url;
        //                 a.download = new Date().getTime() + 'example.xlsx';
        //                 document.body.appendChild(a);
        //                 a.click();
        //                 document.body.removeChild(a);
        //                 URL.revokeObjectURL(url);
        //             });
        //             xhsExcelObjects = [];
        //         }

        //         document.querySelector('#exploreFeeds > div > div.reload > div.btn-wrapper').click();
        //         await postManager.ifLoaded();
        //         await new Promise(resolve => setTimeout(resolve, 3000));
        //         const xhsExcelObjectsNew = await postManager.openPost();
        //         xhsExcelObjects.push(...xhsExcelObjectsNew);
        //     }
        //     const excelWorkBook = await postManager.convertToExcel(xhsExcelObjects);
        //     // 导出 Excel 文件
        //     excelWorkBook.xlsx.writeBuffer().then(buffer => {
        //         const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
        //         const url = URL.createObjectURL(blob);
        //         const a = document.createElement('a');
        //         a.href = url;
        //         a.download = new Date().getTime() + 'example.xlsx';
        //         document.body.appendChild(a);
        //         a.click();
        //         document.body.removeChild(a);
        //         URL.revokeObjectURL(url);
        //     });
        //     xhsExcelObjects = [];
        //     console.log(xhsExcelObjects);
        // });
        window.addEventListener('load', async function () {
            const postManager = new PostManager(21 * 2, 0, 0, createXhsExcelObjectFactory, createExcelJsWorkBookFactory);
            var xhsExcelObjects = await postManager.openLikePost();
            // const xhsExcelObjects = [];
            const restoreNum = 21 * 1;
            while (postManager.openedCount < postManager.expectedOpenCount) {

                if (xhsExcelObjects.length >= restoreNum) {
                    const excelWorkBook = await postManager.convertToExcel(xhsExcelObjects);
                    // 导出 Excel 文件
                    excelWorkBook.xlsx.writeBuffer().then(buffer => {
                        const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = new Date().getTime() + 'example.xlsx';
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                    });
                    xhsExcelObjects = [];
                }
                const xhsExcelObjectsNew = await postManager.openLikePost();
                xhsExcelObjects.push(...xhsExcelObjectsNew);
            }
            const excelWorkBook = await postManager.convertToExcel(xhsExcelObjects);
            // 导出 Excel 文件
            excelWorkBook.xlsx.writeBuffer().then(buffer => {
                const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = new Date().getTime() + 'example.xlsx';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            });
            xhsExcelObjects = [];
            console.log(xhsExcelObjects);
        });
    } else {
        window.addEventListener('load', async function () {
            await new Promise(resolve => setTimeout(resolve, 200));
            await GM.setValue('postDataObject', postDataObject);
            await GM.setValue('postCommentDataObject', postCommentsObject);
            console.log(postCommentsObject);
        });
    }


})();